package com.tgy.MyFlowable.workflow.service.impl;

import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ctsi.core.common.exception.MarsException;
import com.ctsi.core.common.util.$;
import com.ctsi.core.common.util.SecurityUtil;
import com.ctsi.core.common.util.StringPool;
import com.ctsi.core.database.util.PageUtil;
import com.ctsi.workflow.dto.FlwModelDTO;
import com.ctsi.workflow.dto.FlwModelInfoDTO;
import com.ctsi.workflow.enums.FlwModelExceptionEnum;
import com.ctsi.workflow.service.IFlwModelService;
import com.ctsi.workflow.vo.FlwModelVO;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.flowable.bpmn.BpmnAutoLayout;
import org.flowable.bpmn.converter.BpmnXMLConverter;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.bpmn.model.EndEvent;
import org.flowable.bpmn.model.Process;
import org.flowable.common.engine.api.FlowableObjectNotFoundException;
import org.flowable.editor.language.json.converter.BaseBpmnJsonConverter;
import org.flowable.editor.language.json.converter.BpmnJsonConverter;
import org.flowable.engine.ManagementService;
import org.flowable.engine.RepositoryService;
import org.flowable.ui.common.util.XmlUtil;
import org.flowable.ui.modeler.domain.Model;
import org.flowable.ui.modeler.domain.ModelHistory;
import org.flowable.ui.modeler.model.ModelRepresentation;
import org.flowable.ui.modeler.repository.ModelHistoryRepository;
import org.flowable.ui.modeler.repository.ModelRepository;
import org.flowable.ui.modeler.serviceapi.ModelService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamReader;
import java.io.ByteArrayInputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;

/**
 * 流程模型接口实现类
 *
 * @version 1.0
 * @author: wang xiao xiang
 * @date: 2021/8/8 19:43
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class FlwModelServiceImpl implements IFlwModelService {

    /**
     * 默认排序规则
     */
    private static final String DEFAULT_ORDER_BY = "modifiedDesc";

    /**
     * 默认模型类型，流程模型
     */
    private static final Integer DEFAULT_MODEL_TYPE = 0;

    /**
     * 模型资源后缀
     */
    private static final String MODEL_BPMN_SUFFIX_BPMN = ".bpmn";

    /**
     * 模型资源后缀
     */
    private static final String MODEL_BPMN_SUFFIX_XML = ".xml";

    private final BpmnXMLConverter bpmnXmlConverter = new BpmnXMLConverter();

    private final BpmnJsonConverter bpmnJsonConverter = new BpmnJsonConverter();

    private final ModelService modelService;

    private final ModelRepository modelRepository;

    private final ManagementService managementService;

    private final ModelHistoryRepository modelHistoryRepository;

    private final ObjectMapper objectMapper;

    @Autowired
    private HttpServletRequest request;

    @Autowired
    private RepositoryService repositoryService;

    @Override
    public Page<FlwModelVO> list(FlwModelDTO flwModelDTO) {
        List<Model> modelList;
        if ($.isNotEmpty(flwModelDTO)) {
            if ($.isNotEmpty(flwModelDTO.getName())) {
                String name = StringPool.PERCENT + flwModelDTO.getName().trim() + StringPool.PERCENT;
                modelList = modelRepository.findByModelTypeAndFilter(DEFAULT_MODEL_TYPE, name, DEFAULT_ORDER_BY);
            } else {
                modelList = modelRepository.findByModelType(DEFAULT_MODEL_TYPE, DEFAULT_ORDER_BY);
            }
        } else {
            modelList = modelRepository.findByModelType(DEFAULT_MODEL_TYPE, DEFAULT_ORDER_BY);
        }
        List<FlwModelVO> list = modelList.stream().map(model -> $.copy(model, FlwModelVO.class)).collect(Collectors.toList());
        return getPage(flwModelDTO, list);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public String add(FlwModelDTO flwModelDTO) {
        // 检查Key是否存在
        checkModelKeyExists(flwModelDTO.getKey());

        ModelRepresentation modelRepresentation = new ModelRepresentation();
        modelRepresentation.setKey(flwModelDTO.getKey());
        modelRepresentation.setName(flwModelDTO.getName());
        modelRepresentation.setDescription(flwModelDTO.getDescription());
        modelRepresentation.setModelType(DEFAULT_MODEL_TYPE);
        String json = modelService.createModelJson(modelRepresentation);
        Model model = modelService.createModel(modelRepresentation, json, SecurityUtil.getUsername(request).getAccount());
        return model.getId();
    }

    protected void checkModelKeyExists(String modelKey) {
        List<Model> byKeyAndType = modelRepository.findByKeyAndType(modelKey, DEFAULT_MODEL_TYPE);
        if ($.isNotEmpty(byKeyAndType)) {
            throw new FlowableObjectNotFoundException("ModelKey already exists with id " + modelKey);
        }
    }

    @Override
    public void delete(String ids) {
        $.toStrList(ids).forEach(id -> {
            modelService.deleteModel(id);
        });
    }

    @Override
    public String edit(FlwModelDTO flwModelDTO) {
        //获取模型
        Model model = modelService.getModel(flwModelDTO.getId());
        //更新基本信息
        model.setKey(flwModelDTO.getKey());
        model.setName(flwModelDTO.getName());
        model.setDescription(flwModelDTO.getDescription());
        model.setModelType(DEFAULT_MODEL_TYPE);
        //获取模型的editorJson
        ObjectNode modelNode;
        try {
            modelNode = (ObjectNode) objectMapper.readTree(model.getModelEditorJson());
            modelNode.put("name", model.getName());
            modelNode.put("key", model.getKey());
            //更新其editorJson信息
            ObjectNode propertiesNode = (ObjectNode) modelNode.get("properties");
            propertiesNode.put("process_id", model.getKey());
            propertiesNode.put("name", model.getName());
            if ($.isNotEmpty(model.getDescription())) {
                propertiesNode.put("documentation", model.getDescription());
            }
            modelNode.set("properties", propertiesNode);
            model.setModelEditorJson(modelNode.toString());
            modelRepository.save(model);
            return model.getId();
        } catch (Exception e) {
            log.error(">>> 模型编辑失败：{}", e.getMessage());
            throw new MarsException(FlwModelExceptionEnum.MODEL_EDIT_ERROR);
        }
    }

    @Override
    public String importModel(MultipartFile file) {

        String fileName = file.getOriginalFilename();
        if ($.isEmpty(fileName)) {
            throw new MarsException(FlwModelExceptionEnum.MODEL_FILE_SUFFIX_ERROR);
        }
        if (!fileName.endsWith(MODEL_BPMN_SUFFIX_BPMN) && !fileName.endsWith(MODEL_BPMN_SUFFIX_XML)) {
            throw new MarsException(FlwModelExceptionEnum.MODEL_FILE_SUFFIX_ERROR);
        } else {
            try {
                XMLInputFactory xif = XmlUtil.createSafeXmlInputFactory();
                InputStreamReader xmlIn = new InputStreamReader(file.getInputStream(), StandardCharsets.UTF_8);
                XMLStreamReader xtr = xif.createXMLStreamReader(xmlIn);
                BpmnModel bpmnModel = bpmnXmlConverter.convertToBpmnModel(xtr);
                if ($.isEmpty(bpmnModel.getProcesses())) {
                    throw new MarsException(FlwModelExceptionEnum.MODEL_IMPORT_NO_RESOURCE);
                } else {
                    if ($.isEmpty(bpmnModel.getLocationMap())) {
                        BpmnAutoLayout bpmnLayout = new BpmnAutoLayout(bpmnModel);
                        bpmnLayout.execute();
                    }

                    ObjectNode modelNode = bpmnJsonConverter.convertToJson(bpmnModel);
                    Process process = bpmnModel.getMainProcess();
                    String name = process.getId();
                    if ($.isNotEmpty(process.getName())) {
                        name = process.getName();
                    }

                    String description = process.getDocumentation();
                    ModelRepresentation model = new ModelRepresentation();
                    model.setKey(process.getId());
                    model.setName(name);
                    model.setDescription(description);
                    model.setModelType(DEFAULT_MODEL_TYPE);
                    Model resultModel = modelService.createModel(model, modelNode.toString(), SecurityUtil.getUsername(request).getAccount());
                    return resultModel.getId();
                }

            } catch (Exception e) {
                log.error(">>> 模型导入失败：{}", e.getMessage());
                throw new MarsException(FlwModelExceptionEnum.MODEL_IMPORT_ERROR);
            }
        }
    }


    @Override
    public String preview(FlwModelDTO flwModelDTO) {
        Model model = modelService.getModel(flwModelDTO.getId());
        return new String(modelService.getBpmnXML(model));
    }

    @Override
    public void setNew(FlwModelDTO flwModelDTO) {
        Model model = modelService.getModel(flwModelDTO.getModelId());
        String modelHistoryId = flwModelDTO.getId();
        String modelId = model.getId();
        ModelHistory modelHistory = modelService.getModelHistory(modelId, modelHistoryId);
        modelService.reviveProcessModelHistory(modelHistory, SecurityUtil.getUsername(request).getAccount(), null);
    }

    @Override
    public String saveBpmnModel(FlwModelInfoDTO flwModelInfoDTO) {
        Model processModel = modelService.getModel(flwModelInfoDTO.getModelId());
        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(flwModelInfoDTO.getModelXml().getBytes());
        String fileName = flwModelInfoDTO.getFileName();
        if ($.isBlank(fileName)) {
            fileName = processModel.getKey() + MODEL_BPMN_SUFFIX_BPMN;
        }
        if (!fileName.endsWith(MODEL_BPMN_SUFFIX_BPMN) && !fileName.endsWith(MODEL_BPMN_SUFFIX_XML)) {
            throw new MarsException(FlwModelExceptionEnum.MODEL_FILE_SUFFIX_ERROR);
        } else {
            try {
                XMLInputFactory xif = XmlUtil.createSafeXmlInputFactory();
                InputStreamReader xmlIn = new InputStreamReader(byteArrayInputStream, StandardCharsets.UTF_8);
                XMLStreamReader xtr = xif.createXMLStreamReader(xmlIn);
                BpmnModel bpmnModel = bpmnXmlConverter.convertToBpmnModel(xtr);
                bpmnModel.getMainProcess().setId(processModel.getKey());
                bpmnModel.setTargetNamespace(BaseBpmnJsonConverter.NAMESPACE);

                if ($.isEmpty(bpmnModel.getProcesses())) {
                    throw new MarsException(FlwModelExceptionEnum.MODEL_IMPORT_NO_RESOURCE);
                } else {
                    if ($.isEmpty(bpmnModel.getLocationMap())) {
                        BpmnAutoLayout bpmnLayout = new BpmnAutoLayout(bpmnModel);
                        bpmnLayout.execute();
                    }

                    ObjectNode modelNode = bpmnJsonConverter.convertToJson(bpmnModel);
//					Process process = bpmnModel.getMainProcess();
//					String name = process.getId();
//					if ($.isNotEmpty(process.getName())) {
//						name = process.getName();
//					}

//					String description = process.getDocumentation();
//					ModelRepresentation model = new ModelRepresentation();
//					model.setId(processModel.getId());
//					model.setKey(processModel.getKey());
//					model.setName(name);
//					model.setDescription(description);
//					model.setModelType(DEFAULT_MODEL_TYPE);
                    Model resultModel = modelService.saveModel(processModel, modelNode.toString(), flwModelInfoDTO.getSvg().getBytes(), true, "new version", SecurityUtil.getUsername(request).getAccount());
                    return resultModel.getId();
                }
            } catch (Exception e) {
                log.error(">>> 模型导入失败：{}", e.getMessage());
                throw new MarsException(FlwModelExceptionEnum.MODEL_IMPORT_ERROR);
            }
        }
    }

    @Override
    public Page<FlwModelVO> version(FlwModelDTO flwModelDTO) {
        List<ModelHistory> modelHistoryList = modelHistoryRepository.findByModelId(flwModelDTO.getId());
        List<FlwModelVO> collect = modelHistoryList.stream().map(modelHistory -> $.copy(modelHistory, FlwModelVO.class)).sorted(Comparator.comparing(FlwModelVO::getVersion).reversed()).collect(Collectors.toList());
        return getPage(flwModelDTO, collect);
    }

    @Override
    public List<EndEvent> findEndFlowElement(String processDefId) {
        BpmnModel bpmnModel = this.getBpmnModelByProcessDefId(processDefId);
        if (bpmnModel != null) {
            Process process = bpmnModel.getMainProcess();
            return process.findFlowElementsOfType(EndEvent.class);
        } else {
            return null;
        }
    }

    @Override
    public BpmnModel getBpmnModelByProcessDefId(String processDefId) {
        return repositoryService.getBpmnModel(processDefId);
    }

    /**
     * 根据list和请求参数拆分分页数据
     *
     * @param flwModelDTO 分页请求对象
     * @param list        所有List数据列表
     * @return Page 分页对象
     */
    private Page<FlwModelVO> getPage(FlwModelDTO flwModelDTO, List list) {
        Page<FlwModelVO> page = PageUtil.getPage(flwModelDTO);
        long current = page.getCurrent();
        long size = page.getSize();
        if (list.size() > 0) {
            page = PageUtil.getPages(current, size, list);
        }
        return page;
    }
}
